// SPDX-License-Identifier: GPL-2.0
/*
 * This file contains the base functions to manage periodic tick
 * related events.
 *
 * Copyright(C) 2005-2006, Thomas Gleixner <tglx@linutronix.de>
 * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar
 * Copyright(C) 2006-2007, Timesys Corp., Thomas Gleixner
 */

#include <seminix/init.h>
#include <seminix/smp.h>
#include <seminix/irq/manage.h>
#include <devices/clockchips.h>
#include "tick-internal.h"

/*
 * Tick devices
 */
DEFINE_PER_CPU(struct tick_device, tick_cpu_device);

/*
 * tick_do_timer_cpu is a timer core internal variable which holds the CPU NR
 * which is responsible for calling do_timer(), i.e. the timekeeping stuff. This
 * variable has two functions:
 *
 * 1) Prevent a thundering herd issue of a gazillion of CPUs trying to grab the
 *    timekeeping lock all at once. Only the CPU which is assigned to do the
 *    update is handling it.
 *
 * 2) Hand off the duty in the NOHZ idle case by setting the value to
 *    TICK_DO_TIMER_NONE, i.e. a non existing CPU. So the next cpu which looks
 *    at it will take over and keep the time keeping alive.  The handover
 *    procedure also covers cpu hotplug.
 */
int tick_do_timer_cpu __read_mostly = TICK_DO_TIMER_BOOT;

static bool tick_check_percpu(struct clock_event_device *curdev,
                  struct clock_event_device *newdev, int cpu)
{
    if (!cpumask_test_cpu(cpu, newdev->cpumask))
        return false;
    if (cpumask_equal(newdev->cpumask, cpumask_of(cpu)))
        return true;
    /* Check if irq affinity can be set */
    if (newdev->irq >= 0 && !irq_can_set_affinity(newdev->irq))
        return false;
    /* Prefer an existing cpu local device */
    if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))
        return false;
    return true;
}

static bool tick_check_preferred(struct clock_event_device *curdev,
                 struct clock_event_device *newdev)
{
    /* Prefer oneshot capable device */
    if (!(newdev->features & CLOCK_EVT_FEAT_ONESHOT)) {
        if (curdev && (curdev->features & CLOCK_EVT_FEAT_ONESHOT))
            return false;
        if (tick_oneshot_mode_active())
            return false;
    }

    /*
     * Use the higher rated one, but prefer a CPU local device with a lower
     * rating than a non-CPU local device
     */
    return !curdev ||
        newdev->rating > curdev->rating ||
           !cpumask_equal(curdev->cpumask, newdev->cpumask);
}

/*
 * Setup the device for a periodic tick
 */
void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
    /* Now, only support oneshot */
}

/*
 * Setup the tick device
 */
static void tick_setup_device(struct tick_device *td,
                  struct clock_event_device *newdev, int cpu,
                  const struct cpumask *cpumask)
{
    void (*handler)(struct clock_event_device *) = NULL;
    ktime_t next_event = 0;

    /*
     * First device setup ?
     */
    if (!td->evtdev) {
        if (tick_do_timer_cpu == TICK_DO_TIMER_BOOT)
            tick_do_timer_cpu = cpu;
        td->mode = TICKDEV_MODE_PERIODIC;
    } else {
        handler = td->evtdev->event_handler;
        next_event = td->evtdev->next_event;
        td->evtdev->event_handler = clockevents_handle_noop;
    }

    td->evtdev = newdev;

    /*
     * When the device is not per cpu, pin the interrupt to the
     * current cpu:
     */
    if (!cpumask_equal(newdev->cpumask, cpumask))
        irq_set_affinity(newdev->irq, cpumask);

    if (td->mode == TICKDEV_MODE_PERIODIC)
        tick_setup_periodic(newdev, 0);
    else
        tick_setup_oneshot(newdev, handler, next_event);
}

/*
 * Check, if the new registered device should be used. Called with
 * clockevents_lock held and interrupts disabled.
 */
void tick_check_new_device(struct clock_event_device *newdev)
{
    struct clock_event_device *curdev;
    struct tick_device *td;
    int cpu;

    cpu = smp_processor_id();
    td = &per_cpu(tick_cpu_device, cpu);
    curdev = td->evtdev;

    /* cpu local device ? */
    if (!tick_check_percpu(curdev, newdev, cpu))
        return;

    /* Preference decision */
    if (!tick_check_preferred(curdev, newdev))
        return;

    clockevents_exchange_device(curdev, newdev);
    tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
}
